from txosc import dispatch
from Bases import BaseObject

def join_address(*args):
    s = '/'.join(['/'.join(arg.split('/')) for arg in args])
    return s
    
def pack_args(value):
    if isinstance(value, list) or isinstance(value, tuple):
        return value
    elif value is None:
        return []
    return [value]
    
def get_node_path(node):
    parent = node
    path = []
    while parent is not None:
        path.append(parent._name)
        parent = parent._parent
    path.reverse()
    return join_address(*path)

class OSCBaseObject(BaseObject):
    _saved_attributes = ['osc_address']
    def __init__(self, **kwargs):
        self.osc_enabled = False
        address = kwargs.get('osc_address')
        if address is None and hasattr(self, 'osc_address'):
            address = getattr(self, 'osc_address')
        if address is not None:
            self.osc_address = str(address)
        parent = kwargs.get('osc_parent_node')
        if parent is None and hasattr(self, 'osc_parent_node'):
            parent = getattr(self, 'osc_parent_node')
        if parent is not None:
            self.osc_parent_node = parent
        self.osc_enabled = parent is not None and address is not None
        
        if self.osc_enabled:
            self.osc_handlers = {}
            self.osc_child_nodes = set()
            self.osc_node = self.osc_parent_node.add_new_node(name=self.osc_address)
            self.set_osc_address(self.osc_address)
        else:
            self.osc_address = None
            
        super(OSCBaseObject, self).__init__(**kwargs)
            
    def set_osc_address(self, address):
        if self.osc_enabled:
            for c in ['/', ' ']:
                if c in address:
                    address = '_'.join(address.split(c))
            self.osc_address = address
            self.osc_node.setName(address)
            
    def add_osc_child(self, **kwargs):
        if self.osc_enabled:
            address = kwargs.get('address')
            d = {'osc_address':address, 'osc_parent_node':self.osc_node}
            return d
        return {}
        
    def add_osc_handler(self, **kwargs):
        '''Add an OSC handler
        :Parameters:
            'address' : Relative address to self.osc_node. Default is
                        None which will use the address of self.osc_node
            'callbacks' : Dict of {'address':callback} to handle. Default is None
                          which disables callback mode and requires parameters below.
        '''
        if self.osc_enabled:
            address = kwargs.get('address')
            
            if address is None:
                node = self.osc_node
                address = self.osc_address
                kwargs['address'] = address
            else:
                node = self.osc_node.add_new_node(name=address)
            kwargs.setdefault('osc_node', node)
            objhandler = self.osc_handlers.get(address)
            if not objhandler:
                objhandler = OSCHandler(**kwargs)
                self.osc_handlers.update({address:objhandler})
            else:
                objhandler.add_callbacks(**kwargs.get('callbacks', {}))
            return objhandler
            
    def remove_osc_handler(self, **kwargs):
        key = kwargs.get('id')
        if key in self.osc_handlers:
            self.osc_handlers[key].remove_callbacks()
            del self.osc_handlers[key]
    

class OSCHandler(BaseObject):
    def __init__(self, **kwargs):
        super(OSCHandler, self).__init__()
        self.callbacks = {}
        self.address = kwargs.get('address')
        #self.callbacks = kwargs.get('callbacks')
        self.osc_node = kwargs.get('osc_node')
        #self.osc_node.addCallback(self.address+'/*', self.handle_message)
        callbacks = kwargs.get('callbacks')
        self.add_callbacks(**callbacks)
        
    def add_callbacks(self, **kwargs):
        self.callbacks.update(kwargs)
        for key in kwargs.iterkeys():
            self.osc_node.addCallback('%s/%s' % (self.address, key), self.handle_message)
            #print self.osc_node._name, '%s/%s' % (self.address, key)
            
    def remove_callbacks(self):
        for key in self.callbacks.iterkeys():
            self.osc_node.removeCallback('%s/%s' % (self.address, key), self.handle_message)
            
    def handle_message(self, message, hostaddr):
        address = message.address
        method = address.split('/')[-1:][0]
        #print 'received: address=%s, method=%s, args=%s' % (address, method, message.getValues())
        cb_kwargs = dict(method=method, address=address, values=message.getValues())
        if self.osc_node.get_client_cb:
            cb_kwargs['client'] = self.osc_node.get_client_cb(hostaddr=hostaddr)
        else:
            print 'no callback!!!!'
        if method in self.callbacks:
            #print 'osc_callback: ', address, message.getValues()
            self.callbacks[method](**cb_kwargs)
        if '*' in self.callbacks:
            self.callbacks['*'](**cb_kwargs)
                    
    def send_methods(self):
        pass

class OSCNode(BaseObject, dispatch.Receiver):
    _saved_class_name = 'OSCNode'
    _saved_child_objects = ['_childNodes']
    def __init__(self, **kwargs):
        #super(OSCNode, self).__init__()
        BaseObject.__init__(self, **kwargs)
        dispatch.Receiver.__init__(self)
        self.register_signal('child_added')
        
        if 'name' in kwargs:
            self.setName(kwargs.get('name'))
        if 'parent' in kwargs:
            self.setParent(kwargs.get('parent'))
            self._oscMaster = self._parent._oscMaster
            self.get_client_cb = self._parent.get_client_cb
        else:
            self._oscMaster = kwargs.get('oscMaster', False)
            self.get_client_cb = kwargs.get('get_client_cb')
        self.is_root_node = kwargs.get('root_node', False)
        self.transmit_callback = kwargs.get('transmit_callback')
        
    @property
    def oscMaster(self):
        return self._oscMaster
    @oscMaster.setter
    def oscMaster(self, value):
        if value != self.oscMaster:
            self.get_root_node()._set_oscMaster(value)
        
    def _set_oscMaster(self, value):
        self._oscMaster = value
        for child in self._childNodes.itervalues():
            child._set_oscMaster(value)
        
    def add_new_node(self, **kwargs):
        name = kwargs.get('name')
        address = kwargs.get('address')
        parent = kwargs.get('parent', self)
        if parent == self:
            if address:
                path = self._patternPath(address)
                nodes = self.search_nodes(path)
                node = self
                if nodes:
                    i = len(nodes) - 1
                    if nodes[i] == True:
                        return nodes[i-1]
                    node = nodes[i-1]
                    path = path[i:]
                    
                for s in path[1:]:
                    node = node.add_new_node(name=s)
                return node
            if name in self._childNodes:
                return self._childNodes[name]
            return OSCNode(name=name, parent=self)
        if self._parent is not None:
            return self._parent.add_new_node(**kwargs)
        return None
        
    def search_nodes(self, path, index=0, result=None):
        if result is None:
            result = []
        if index != 0:
            result.append(self)
        if index >= len(path)-1:
            if path[index] == self._name:
                result.append(True)
            else:
                result.append(False)
            return result
        index += 1
        child = self._childNodes.get(path[index])
        if child:
            child.search_nodes(path, index, result)
        else:
            if result and result[len(result)-1] != True:
                result.append(False)
        return result
            
        
    def send_message(self, **kwargs):
        '''Send an OSC message object up through the tree and finally
        out of the root node
        :Parameters:
            'address' : relative OSC address from the node that is sending
            'value' : OSC args to send, can be list, tuple or single value
                        of any type supported by OSC
        '''
        if self.is_root_node:
            self.transmit_callback(**kwargs)
            return
        address = kwargs.get('address')
        if type(address) == str:
            address = [address]
        address[0:0] = [self._name]
        kwargs['address'] = address
        self._parent.send_message(**kwargs)
        
    def get_full_path(self, address=None):
        if self.is_root_node:
            return address
        if address is None:
            address = []
        address[0:0] = [self._name]
        return self._parent.get_full_path(address)
        
    def get_root_node(self):
        if self.is_root_node:
            return self
        return self._parent.get_root_node()
        
    def addNode(self, name, instance):
        super(OSCNode, self).addNode(name, OSCNode())
        self.emit('child_added')
